# Copyright 2011 Google Inc. All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#     http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

"""SheetMash API handlers

Any API access must have the legitimate "authkey" parameter must be passed
with other parameters.
"""
import logging
import re
import StringIO

from google.appengine.api import namespace_manager
from google.appengine.ext import db
from google.appengine.ext import webapp
from google.appengine.ext.webapp.util import run_wsgi_app

import excel_utils
import models
import utils


def GetFilesetNameAndVersionFromFilename(filename):
    """Extracts spreadsheet name and version from filename.

    Arguments:
      filename: unicode object representing the uploaded filename.

    Returns:
      A tuple of sheetname and version, both are unicode objects, or None
      if extraction failed.
    """
    m = re.match(r'(?P<sheetname>.*)-(?P<version>[0-9]+)\.xls$', filename)
    if m is None:
        return None
    d = m.groupdict()
    if 'sheetname' in d and 'version' in d:
        return (d['sheetname'], d['version'])
    else:
        return None


class BaseAPIRequestHandler(webapp.RequestHandler):
    """Base class for API handlers.

    Derived classes should not overwrite get() or post() instead should
    overwrite get_main() and post_main().

    This class's get() and post() checks the "authkey" parameter of
    self.request. If authkey is not found or the found authkey is not in
    AuthKeyRecord, the handler immediately returns 403. Otherwise this will
    set up a namespace with "authkey" as name and call get_main() or
    post_main().
    """
    def __authenticate(self):
        """Authenticates access.

        Returns
          True or False. True when the access is valid. False otherwise.
        """
        authkey = self.request.get('authkey')
        if authkey and models.AuthKeyRecord.get_by_key_name(authkey):
            namespace_manager.set_namespace(authkey)
            return True
        else:
            return False

    def get(self):
        if self.__authenticate():
            self.get_main()
        else:
            self.response.set_status(403)
            self.response.out.write('Access denied')

    def post(self):
        if self.__authenticate():
            self.post_main()
        else:
            self.response.set_status(403)
            self.response.out.write('Access denied')

    def get_main(self):
        raise NotImplementedError

    def post_main(self):
        raise NotImplementedError


class AddHandler(BaseAPIRequestHandler):
    """Adds an Excel file to the "key" namespace.

    Parameters:
      authkey
      filename
      content
    """
    def post_main(self):
        filename = self.request.get('filename')
        content = self.request.get('content')

        # Saves content
        blobfile = models.BlobFile.Open(filename)
        blobfile.data = content
        blobfile.put()

        # Updates file record.
        retval = GetFilesetNameAndVersionFromFilename(filename)
        if retval is None:
            self.response.set_status(400)
            self.response.out.write('Bad filename: %s' % filename)
            return

        (sheetname, version) = retval
        fileset = models.BlobFileSet.Open(sheetname)
        fileset.version_filename_map[version] = filename
        if version > fileset.latest_version:
            fileset.latest_version = version
        fileset.Sync()

        self.response.out.write('Success')


class DeleteAllHandler(BaseAPIRequestHandler):
    """Deletes all files in the "key" namespace.

    Parameters:
      authkey
    """
    def post_main(self):
        query = models.BlobFileSet.all()
        filesets = utils.FetchAll(query)
        for fileset in filesets:
            for filename in fileset.version_filename_map.keys():
                blobfile = models.BlobFile.Open(filename)
                blobfile.delete()
        db.delete(filesets)
        self.response.out.write('Success')


class ListHandler(BaseAPIRequestHandler):
    """Gets list of all files in the "key" namespace.

    Parameters:
      authkey
    """
    def post_main(self):
        query = models.BlobFileSet.all()
        filesets = utils.FetchAll(query)
        self.response.headers['Content-Type'] = 'text/plain; charset=utf-8'
        for fileset in filesets:
            versions = fileset.version_filename_map.keys()
            self.response.out.write(
                fileset.fileset_name() + ':' + ','.join(versions) + '\n')


class MergeLatestHandler(BaseAPIRequestHandler):
    """Merges first sheets of the latest files and returns a single-sheet file.

    Parameters:
      authkey
    """
    def post_main(self):
        query = models.BlobFileSet.all()
        filesets = utils.FetchAll(query)

        contents = []
        for fileset in filesets:
            filename = fileset.version_filename_map[fileset.latest_version]
            blobfile = models.BlobFile.Open(filename)
            contents.append(blobfile.data)
        
        logfile = StringIO.StringIO()
        output = excel_utils.MergeFirstSheetsToOneSheet(
            contents, 1, 'merged', logfile=logfile)
        logging.warning('During Excel file merging\n' + logfile.getvalue())
        logfile.close()

        self.response.headers['Content-Type'] = 'application/vnd.ms-excel'
        self.response.headers.add_header('Content-Disposition',
                                         'attachment', filename='merged.xls')
        self.response.out.write(output)


class MergeLatestAllHandler(BaseAPIRequestHandler):
    """Merges all sheets of the latest files and returns a single-sheet file.

    Parameters:
      authkey
    """
    def post_main(self):
        query = models.BlobFileSet.all()
        filesets = utils.FetchAll(query)

        contents = []
        for fileset in filesets:
            filename = fileset.version_filename_map[fileset.latest_version]
            blobfile = models.BlobFile.Open(filename)
            contents.append(blobfile.data)
        
        logfile = StringIO.StringIO()
        output = excel_utils.MergeAllSheetsToOneSheet(
            contents, 1, 'merged', logfile=logfile)
        logging.warning('During Excel file merging\n' + logfile.getvalue())
        logfile.close()

        self.response.headers['Content-Type'] = 'application/vnd.ms-excel'
        self.response.headers.add_header('Content-Disposition',
                                         'attachment', filename='merged.xls')
        self.response.out.write(output)


application = webapp.WSGIApplication(
    [('/api/add', AddHandler),
     ('/api/delete_all', DeleteAllHandler),
     ('/api/list', ListHandler),
     ('/api/merge_latest', MergeLatestHandler),
     ('/api/merge_latest_all', MergeLatestAllHandler)],
    debug=True)


def main():
    run_wsgi_app(application)


if __name__ == '__main__':
    main()
